package com.avengers.core;

import com.alibaba.druid.wall.WallFilter;
import com.avengers.core.handler.JobNoHandler;
import com.avengers.core.kit.StringKit;
import com.avengers.core.model.AvengersActiveRecordPlugin;
import com.avengers.core.model.ScopeChecker;
import com.jfinal.config.Constants;
import com.jfinal.config.Handlers;
import com.jfinal.config.JFinalConfig;
import com.jfinal.config.Plugins;
import com.jfinal.kit.PathKit;
import com.jfinal.kit.Prop;
import com.jfinal.kit.PropKit;
import com.jfinal.plugin.activerecord.ActiveRecordPlugin;
import com.jfinal.plugin.druid.DruidPlugin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.Query;
import java.io.File;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

/**
 * @author yu.wang
 * @since 2018-04-03 下午6:21
 */
public abstract class AbstractEnhancerConfig extends JFinalConfig {
    private static final Logger logger = LoggerFactory.getLogger(AbstractEnhancerConfig.class);
    private static String projectName = null;
    private static String subClassName = null;
    private static Integer tomcatPort = null;

    private static boolean SHOW_SQL = false;
    private static boolean SHOW_SQL_TIME = false;

    public static void setShowSql(boolean showSql) {
        SHOW_SQL = showSql;
    }

    public static void setShowSqlTime(boolean showSqlTime) {
        SHOW_SQL_TIME = showSqlTime;
    }

    public static boolean isShowSql() {
        return SHOW_SQL;
    }

    public static boolean isShowSqlTime() {
        return SHOW_SQL_TIME;
    }

    private static final String SCOPE_DEV = "dev";
    private ScopeChecker scopeChecker;

    /**
     * 环境配置文件
     */
    protected abstract String initScopePropName();

    @Override
    public final void configConstant(Constants me) {
        subClassName = this.getClass().getSimpleName();
        scopeChecker = new ScopeChecker(initScopePropName());
        initConstant(me);
    }

    public Set<String> getAllowIpList() {
        return scopeChecker.getAllowIpList();
    }

    public static String getProjectName() {
        if (StringKit.isBlank(projectName)) {
            String projectPath = System.getProperty("catalina.home");
            if (StringKit.notBlank(projectPath)) {
                projectName = projectPath.substring(projectPath.lastIndexOf(File.separator) + 1);
            }
            if (StringKit.isBlank(projectName)) {
                projectName = subClassName;
            }
        }
        return projectName;
    }

    public static String getCurrentIp() {
        return ScopeChecker.getCurrentIp();
    }

    /**
     * 暂仅支持tomcat
     **/
    public static Integer getPort() {
        if (null != tomcatPort) {
            return tomcatPort;
        }
        try {
            MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer();
            Set<ObjectName> objectNames = beanServer.queryNames(new ObjectName("*:type=Connector,*"),
                    Query.match(Query.attr("protocol"), Query.value("HTTP/1.1")));
            tomcatPort = Integer.valueOf(objectNames.iterator().next().getKeyProperty("port"));
        } catch (Exception e) {
            if (!SCOPE_DEV.equals(ScopeChecker.getScope())) {
                logger.error(e.getMessage(), e);
            }
        }
        if (null == tomcatPort) {
            tomcatPort = 0;
        }
        return tomcatPort;
    }

    /**
     * 初始化配置
     */
    protected abstract void initConstant(Constants me);

    /**
     * 数据库配置文件
     */
    protected abstract String initDbPropName();

    @Override
    public void configPlugin(Plugins me) {
        String dbPropName = initDbPropName();
        if (null != dbPropName && !dbPropName.isEmpty()) {
            try {
                Prop prop = PropKit.use(dbPropName);
                if (prop.isEmpty()) {
                    try {
                        prop = PropKit.use(scopeChecker.getFullPropPath(dbPropName));
                    } catch (Exception ignore) {
                    }
                }
                if (!prop.isEmpty()) {
                    loadDBPlugin(prop, me);
                } else {
                    logger.debug("skip load DbPlugin, because: Not found db prop in file {}", dbPropName);
                }
            } catch (Exception e) {
                if (!e.getMessage().contains("file not found")) {
                    throw e;
                }
                logger.debug("skip load DbPlugin, because: {}", e.getMessage(), e);
            }
        }
        configPluginWithoutDb(me);
    }

    protected String getFullPropPath(String fileName) {
        return scopeChecker.getFullPropPath(fileName);
    }

    /**
     * 非数据库配置
     */
    protected abstract void configPluginWithoutDb(Plugins me);

    public static void loadDBPlugin(Prop prop, Plugins me) {
        if (null == prop || null == me || prop.isEmpty()) {
            return;
        }
        String mainConfigName = prop.get("main.db.name");
        SHOW_SQL = prop.getBoolean("sql.show", false);
        SHOW_SQL_TIME = prop.getBoolean("sql.time.show", false);
        Properties properties = prop.getProperties();
        Map<String, AvengersActiveRecordPlugin> arpMap = new HashMap<>(20);
        logger.info("DB prop main name is【{}】", mainConfigName);
        for (Object o : properties.keySet()) {
            if (null == o) {
                continue;
            }
            String key = o.toString();
            if (key.contains("mapping.path")) {
                String mappingPath = properties.getProperty(key);
                String[] configModules = key.replace("mapping.path.", "").split("\\.");
                String configName = configModules[0];
                String moduleName = configModules.length == 1 ? configName : configModules[1];
                logger.info("loading DB【{}】module【{}】", configName, moduleName);

                try {
                    AvengersActiveRecordPlugin arp = arpMap.get(configName);
                    if (null == arp) {
                        DruidPlugin druidPlugin = createDruidPlugin(prop, configName);
                        me.add(druidPlugin);
                        if (mainConfigName.equals(configName)) {
                            arp = new AvengersActiveRecordPlugin(druidPlugin);
                        } else {
                            arp = new AvengersActiveRecordPlugin(configName, druidPlugin);
                        }
                        arp.setTransactionLevel(Connection.TRANSACTION_READ_COMMITTED);
                        arp.setShowSql(SHOW_SQL);
                        loadSqlTemplate(arp, properties, configName);
                        arpMap.put(configName, arp);
                        me.add(arp);
                    }
                    Class<?> clazz = Class.forName(mappingPath);
                    Method mappingMethod = clazz.getMethod("mapping", ActiveRecordPlugin.class);
                    mappingMethod.invoke(clazz, arp);
                } catch (Exception e) {
                    logger.error("loading DB【{}】module【{}】was error :", configName, moduleName, e);
                }
            }
        }
    }

    private static void loadSqlTemplate(AvengersActiveRecordPlugin arp, Properties prop, String configName) {
        String templateKey = configName + ".sql.template.path";
        try {
            for (Object o : prop.keySet()) {
                String key = o.toString();
                if (key.contains(templateKey)) {
                    String sqlTemplatePath = prop.getProperty(key);
                    if (null != sqlTemplatePath && !sqlTemplatePath.isEmpty()) {
                        String dirSeparator = File.separator;
                        int indexOfLastSeparator = sqlTemplatePath.lastIndexOf(dirSeparator);
                        String classPath;
                        String templateName;
                        if (indexOfLastSeparator >= 0) {
                            classPath = sqlTemplatePath.substring(0, indexOfLastSeparator);
                            templateName = sqlTemplatePath.substring(indexOfLastSeparator + 1);
                        } else {
                            classPath = dirSeparator;
                            templateName = sqlTemplatePath;
                        }
                        String fullPath = PathKit.getRootClassPath() + (classPath.startsWith(dirSeparator) ? classPath : dirSeparator + classPath);
                        File file = new File(fullPath);
                        if (file.exists()) {
                            logger.info("loading sql template【{}】", sqlTemplatePath);
                            arp.setBaseSqlTemplatePath(fullPath);
                            arp.addSqlTemplate(templateName);
                        } else {
                            logger.info("not found and unload the sql template【{}】", sqlTemplatePath);
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error("loading sql template key【{}】error: {}", templateKey, e.getMessage(), e);
        }
    }

    public static DruidPlugin createDruidPlugin(Prop prop, String configName) {
        String jdbcUrl = prop.get(configName + ".jdbc.url");
        String jdbcUser = prop.get(configName + ".jdbc.user");
        String jdbcPassword = prop.get(configName + ".jdbc.password");
        Integer jdbcInitialSize = prop.getInt(configName + ".jdbc.initial.size");
        Integer jdbcMinIdle = prop.getInt(configName + ".jdbc.min.idle");
        Integer jdbcMaxActive = prop.getInt(configName + ".jdbc.max.active");
        DruidPlugin druidPlugin = new DruidPlugin(jdbcUrl, jdbcUser, jdbcPassword);
//         StatFilter提供JDBC层的统计信息
//        druidPlugin.addFilter(new StatFilter());
        // WallFilter的功能是防御SQL注入攻击
        WallFilter wallDefault = new WallFilter();
        druidPlugin.addFilter(wallDefault);
        if (null != jdbcInitialSize) {
            druidPlugin.setInitialSize(jdbcInitialSize);
        }
        if (null != jdbcMinIdle) {
            druidPlugin.setMinIdle(jdbcMinIdle);
        }
        if (null != jdbcMaxActive) {
            druidPlugin.setMaxActive(jdbcMaxActive);
        }
        return druidPlugin;
    }

    @Override
    public final void configHandler(Handlers me) {
        me.add(new JobNoHandler());
        handlerConfig(me);
    }

    protected abstract void handlerConfig(Handlers me);
}
